package com.hyy.lotterysystem.service.mq;

import cn.hutool.core.date.DateUtil;
import com.hyy.lotterysystem.common.exception.ServiceException;
import com.hyy.lotterysystem.common.utils.JacksonUtil;
import com.hyy.lotterysystem.common.utils.MailUtil;
import com.hyy.lotterysystem.controller.param.DrawPrizeParam;
import com.hyy.lotterysystem.dao.dataobject.ActivityPrizeDO;
import com.hyy.lotterysystem.dao.dataobject.WinningRecordDO;
import com.hyy.lotterysystem.dao.mapper.ActivityPrizeMapper;
import com.hyy.lotterysystem.dao.mapper.WinningRecordMapper;
import com.hyy.lotterysystem.service.DrawPrizeService;
import com.hyy.lotterysystem.service.activitystatus.ActivityStatusManager;
import com.hyy.lotterysystem.service.dto.ConvertActivityStatusDTO;
import com.hyy.lotterysystem.service.enums.ActivityPrizeStatusEnum;
import com.hyy.lotterysystem.service.enums.ActivityPrizeTiersEnum;
import com.hyy.lotterysystem.service.enums.ActivityStatusEnum;
import com.hyy.lotterysystem.service.enums.ActivityUserStatusEnum;
import jakarta.annotation.Resource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import static com.hyy.lotterysystem.common.config.DirectRabbitConfig.QUEUE_NAME;


@Component
@RabbitListener(queues=QUEUE_NAME)
public class MqConsumer {

    private static Logger logger= LoggerFactory.getLogger(MqConsumer.class);


    @Autowired
    private DrawPrizeService drawPrizeService;

    @Resource(name = "asyncServiceExecutor")
    private ThreadPoolTaskExecutor threadPoolTaskExecutor;


    @Autowired
    private MailUtil mailUtil;

    @Autowired
    private ActivityStatusManager activityStatusManager;

    @Autowired
    private ActivityPrizeMapper activityPrizeMapper;
    @Autowired
    private WinningRecordMapper winningRecordMapper;

    //接收mq消息，用来实际处理抽奖名单
    @RabbitHandler
    public void process(Map<String, String> message) {
        // 成功接收到队列中的消息
        logger.info("MQ成功接收到消息，message:{}",
                JacksonUtil.writeValueAsString(message));

        //把messageData（DrawPrizeParam对象的json字符串）转程Java对象
        String paramString = message.get("messageData");
        DrawPrizeParam param = JacksonUtil.readValue(paramString, DrawPrizeParam.class);



        // 处理抽奖的流程

        try {

            // 校验抽奖请求是否有效
            // 1、有可能前端发起两个一样的抽奖请求，对于param来说也是一样的两个请求
            // 2、param：最后一个奖项-》
            //      处理param1：活动完成、奖品完成
            //      处理param2: 回滚活动、奖品状态

            //检查DrawPrizeParam对象是否有效（活动、奖品、人员等信息是否有效）
            if (!drawPrizeService.checkDrawPrizeParam(param)) {
                return;
            }

            // 状态扭转处理（重要！！ 设计模式--》修改了库中活动、奖品、人员参与活动的状态）
            statusConvert(param);

            // 保存中奖者名单
            List<WinningRecordDO> winningRecordDOList =
                    drawPrizeService.saveWinnerRecords(param);

//            // 通知中奖者（邮箱、短信）
//            // 抽奖之后的后续流程，异步（并发）处理
            syncExecute(winningRecordDOList);

        } catch (ServiceException e) {
            logger.error("处理 MQ 消息异常！{}:{}", e.getCode(), e.getMessage(), e);
            // 需要保证事务一致性（回滚）
                rollback(param);
            // 抛出异常: 消息重试（解决异常：代码bug、网络问题、服务问题）
            throw e;

        } catch (Exception e) {
            logger.error("处理 MQ 消息异常！", e);
            // 需要保证事务一致性（回滚）
            rollback(param);
            // 抛出异常
            throw e;
        }
    }



    /**
     * 回滚中奖记录：删除奖品下的中奖者
     *
     * @param param
     */
    private void rollbackWinner(DrawPrizeParam param) {
        drawPrizeService.deleteRecords(param.getActivityId(), param.getPrizeId());
    }

    private boolean winnerNeedRollback(DrawPrizeParam param) {
        // 判断活动中的奖品是否存在中奖者
        int count = winningRecordMapper.countByAPId(param.getActivityId(), param.getPrizeId());
        return count > 0;
    }

    /**
     * 恢复相关状态
     *
     * @param param
     */
    private void rollbackStatus(DrawPrizeParam param) {
        // 涉及状态的恢复，使用 ActivityStatusManager
        ConvertActivityStatusDTO convertActivityStatusDTO = new ConvertActivityStatusDTO();
        convertActivityStatusDTO.setActivityId(param.getActivityId());
        convertActivityStatusDTO.setTargetActivityStatus(ActivityStatusEnum.RUNNING);
        convertActivityStatusDTO.setPrizeId(param.getPrizeId());
        convertActivityStatusDTO.setTargetPrizeStatus(ActivityPrizeStatusEnum.INIT);
        convertActivityStatusDTO.setUserIds(
                param.getWinnerList().stream()
                        .map(DrawPrizeParam.Winner::getUserId)
                        .collect(Collectors.toList())
        );
        convertActivityStatusDTO.setTargetUserStatus(ActivityUserStatusEnum.INIT);
        activityStatusManager.rollbackHandlerEvent(convertActivityStatusDTO);

    }



  /**
   *    判断活动+奖品+人员表相关状态是否已经扭转：
   *      扭转状态时，保证了事务一致性，要么都扭转了，要么都没扭转（不包含活动）：
   *      因此，只用判断 人员/奖品 是否扭转过，就能判断出状态是否全部扭转
   *      不能判断活动是否已经扭转
   *      结论：判断奖品状态是否扭转，就能判断出全部状态是否扭转
   *      */
    private boolean statusNeedRollback(DrawPrizeParam param) {

        ActivityPrizeDO activityPrizeDO =
                activityPrizeMapper.selectByAPId(
                        param.getActivityId(), param.getPrizeId()
                );
        // 已经扭转了，需要回滚
        return activityPrizeDO.getStatus()
                .equalsIgnoreCase(
                        ActivityPrizeStatusEnum.COMPLETED.name()
                );

    }

    /**
     * 处理抽奖异常的回滚行为：恢复处理请求之前的库表状态
     *
     * @param param
     */
    private void rollback(DrawPrizeParam param) {

        // 1、回滚状态：活动、奖品、人员
        // 状态是否需要回滚
        if (!statusNeedRollback(param)) {
            // 不需要：return
            return;
        }

        // 需要回滚: 回滚
        rollbackStatus(param);

        // 2、回滚中奖者名单
        // 是否需要回滚
        if (!winnerNeedRollback(param)) {
            // 不需要：return
            return;
        }
        // 需要: 回滚
        rollbackWinner(param);

    }

    /**
     * 并发处理抽奖后续流程
     *
     * @param winningRecordDOList
     */
    private void syncExecute(List<WinningRecordDO> winningRecordDOList) {
        // 通过线程池 threadPoolTaskExecutor
        // 扩展：加入策略模式或者其他设计模式来完成后续的异步操作
        // 短信通知 改成站内通知
//        threadPoolTaskExecutor.execute(()->sendMessage(winningRecordDOList));
        // 邮件通知
        threadPoolTaskExecutor.execute(()->sendMail(winningRecordDOList));
    }

    /**
     * 发邮件
     *
     * @param winningRecordDOList
     */
    public void sendMail(List<WinningRecordDO> winningRecordDOList) {
        if (CollectionUtils.isEmpty(winningRecordDOList)) {
            logger.info("中奖列表为空，不用发邮件！");
            return;
        }
        for (WinningRecordDO winningRecordDO : winningRecordDOList) {
            String context = "<div style=\"font-family: 'Microsoft YaHei', Arial, sans-serif; color: #333; background: #f9f9f9; padding: 24px; border-radius: 8px; max-width: 500px; margin: auto;\">" +
                    "<h2 style=\"color: #4CAF50;\">🎉 恭喜中奖！</h2>" +
                    "<p>Hi，<b style=\"color: #2196F3;\">" + winningRecordDO.getWinnerName() + "</b>：</p>" +
                    "<p>恭喜你在 <b style=\"color: #FF9800;\">" + winningRecordDO.getActivityName() + "</b> 活动中获得 <b style=\"color: #E91E63;\">" +
                    ActivityPrizeTiersEnum.forName(winningRecordDO.getPrizeTier()).getMessage() + "</b>：" +
                    "<b style=\"color: #9C27B0;\">" + winningRecordDO.getPrizeName() + "</b>！</p>" +
                    "<p>获奖时间为 <b>" + DateUtil.formatTime(winningRecordDO.getWinningTime()) + "</b>，请尽快在官网联系活动组织人，领取您的奖励！</p>" +
                    "<hr style=\"border:none;border-top:1px solid #eee;\"/>" +
                    "<p style=\"font-size:12px;color:#999;\">如有疑问，请联系活动主办方。</p>" +
                    "</div>";

            mailUtil.sendMailAndHtml(winningRecordDO.getWinnerEmail(),
                    "中奖通知", context); // 第四个参数true表示发送HTML格式
        }
    }


    //        // 问题：
//        // 1、活动状态扭转有依赖性，导致代码维护性差
//        // 2、状态扭转条件可能会扩展，当前写法，扩展性差，维护性差
        // 3、代码的灵活性、扩展性、维护性极差
        // 解决方案：设计模式（责任链设计模式、策略模式）
    /**
    * 状态扭转
    *活动 、 奖品 、人员
    * @param param
    * */
    private void statusConvert(DrawPrizeParam param) {


        //1、把param映射到ConvertActivityStatusDTO
        ConvertActivityStatusDTO convertActivityStatusDTO = new ConvertActivityStatusDTO();

        convertActivityStatusDTO.setActivityId(param.getActivityId());
        convertActivityStatusDTO.setTargetActivityStatus(ActivityStatusEnum.COMPLETED);
        convertActivityStatusDTO.setPrizeId(param.getPrizeId());
        convertActivityStatusDTO.setTargetPrizeStatus(ActivityPrizeStatusEnum.COMPLETED);
        convertActivityStatusDTO.setUserIds(
                param.getWinnerList().stream()
                        .map(DrawPrizeParam.Winner::getUserId)
                        .collect(Collectors.toList())
        );
        convertActivityStatusDTO.setTargetUserStatus(ActivityUserStatusEnum.COMPLETED);

        //2、1策略模式+责任链模式处理状态转换
        activityStatusManager.handlerEvent(convertActivityStatusDTO);   }


}
